fix(isRFC3339): reject impossible calendar dates - #2841
Conversation
isRFC3339 was a pure regex test, so it accepted dates that cannot exist:
isRFC3339('2021-02-30T00:00:00Z') // true
isRFC3339('2021-04-31T00:00:00Z') // true
isRFC3339('2021-02-29T00:00:00Z') // true (2021 is not a leap year)
RFC 3339 section 5.6 caps date-mday at the number of days in the given
month and year, which a regex cannot express. The existing tests already
treat impossible dates as invalid (month 13, month 00, day 00); this extends
that to the day-of-month maximum and the leap-year rule.
The day is compared against a per-month maximum with a leap-year check for
February. The seconds field is never inspected, so the leap-second value
14:53:60Z stays valid.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2841 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 114 114
Lines 2598 2605 +7
Branches 658 661 +3
=========================================
+ Hits 2598 2605 +7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
nrps9909
left a comment
There was a problem hiding this comment.
Reviewed 30fb2178b47d71d491a89cf771412dea617b54e3 against d4e02eea6b1e831b01857214982b8389dc46bbc9. No blocker found in this calendar-day change.
Independent validation on Node 24.15.0:
- Enumerated all four-digit years
0000–9999, all 12 months and days00–32: 3,960,000 timestamps. An independent oracle usesDate.setUTCFullYear/ UTC month boundaries, avoiding the 0–99 constructor remapping. Base accepts 67,575 impossible dates; head has zero mismatches and rejects no previously accepted valid calendar date in this corpus. - Another 5,897 checks per form pass against source, generated Node, browser and minified browser APIs. These cover low years, century boundaries, month ends, supported separators/case/fractions/offsets, malformed fields and non-string errors.
npm testpasses: 321 tests, generated builds and lint; statements, functions and lines are 100% covered. All 13 currently exposed upstream checks pass on this head.
The arithmetic matches the Gregorian month/day restriction in RFC 3339 sections 5.6–5.7. This review is scoped to calendar dates: the existing permissive handling of second 60 is preserved, and these results do not claim historical leap-second validation or complete RFC conformance.
Validation performed with Codex assistance.
isRFC3339is a pure regex test, so it returnstruefor dates that cannot exist:RFC 3339 section 5.6 restricts
date-mdayto the number of days in the given month and year, which the regex cannot express ([12]\d|0[1-9]|3[01]allows 01-31 for every month). The existing test block already treats impossible dates as invalid (2009-13-19, month00, day00); this extends the same intent to the day-of-month maximum and the leap-year rule. For contrast,isISO8601(..., { strict: true })already rejects2021-02-30.Fix
After the regex passes, compare the day against a per-month maximum, with a leap-year check for February:
The comparison is arithmetic rather than
new Date(...), which avoids the legacy two-digit-year behavior of theDateparser for RFC 3339's valid years below 100. The seconds field is never inspected, so the leap-second value14:53:60Z(already in the valid list) stays valid.Tests
Extended the RFC 3339 block in
test/validators.test.js:2020-02-29and2000-02-29(leap day, and the div-by-400 century case)2021-02-30,2021-04-31,2021-06-31,2021-02-29, and1900-02-29(the div-by-100 non-leap century case)